/* 
 * 	This file is part of VPN Monitor Project 7..

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package monitor.traffic;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.util.EnumMap;
import java.util.Map;

import monitor.interfaces.TrafficDataPackage;
import monitor.interfaces.TrafficPackage;
import monitor.interfaces.EnumTypes.Keys;
import monitor.interfaces.EnumTypes.Types;
import monitor.utility.Time;

/**
 * Model class for traffic contains information about traffic
 * for every Key, it also contains if there is a limit set
 * and what this limit then is.
 */
class Traffic implements Serializable, TrafficPackage{
	private static final long serialVersionUID = -3665030997686488983L;
	private transient static final Keys[] keys = Keys.values();
	private transient int[] num = null, update = null;
	private final Types type;
	private EnumMap<Keys, Data> traffic = null;
	private int[] dates = null;
	
	/**
	 * Constructor, creates a new traffic instance where
	 * traffic data for every key are set to zero, no limits are active.
	 * @param type What type this instance should keep track of.
	 */
	Traffic(Types type){
		this.type = type;
		traffic =  new EnumMap<Keys, Data>(Keys.class);
		dates = Time.getInstance().getDates();
		for(Keys s : keys)
			traffic.put(s, new Data());
		initTransient();			
	}
	
	int[] getDates(){
		return dates.clone();
	}
	
	/**
	 * Initiate data that isn't serialized, run
	 * each time a new object is created or when
	 * it's deserialized.
	 */
	private void initTransient(){
		// initiate int[] num
		Keys[] store = Keys.store();
		num = new int[store.length];
		for(int i=0;i<store.length;i++)
			num[i] = store[i].number();
		// initiate int[] update
		Keys[] update = Keys.limits();
		this.update = new int[update.length];
		for(int i=0;i<update.length;i++)
			this.update[i] = update[i].number();
				
		// set session to 0
		traffic.get(Keys.SESSION).traffic = 0;
	}
	
	private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException{
		in.defaultReadObject();
		initTransient();
	}
	
	@Override
	public TrafficDataPackage getValue(Keys key){
		return traffic.get(key).copy();
	}
	
	@Override
	public TrafficDataPackage[] getValues() {
		Data[] array = new Data[traffic.size()];
		return traffic.values().toArray(array);
	}
	
	@Override
	public void copyToEnumMap(EnumMap<Keys, TrafficDataPackage> map){
		map.putAll(traffic);
	}
	
	@Override
	public Types getType(){
		return type;
	}
	
	/**
	 * Change what limits should be activated or deactivated.
	 * @param activeLimits Keys to those limits that should be changed, by
	 * 						default are all limits set to false, in other words
	 * 						there is none.
	 */
	void changeActiveLimits(EnumMap<Keys, Boolean> activeLimits){
		for(Map.Entry<Keys, Boolean> e : activeLimits.entrySet()){
			Data data = traffic.get(e.getKey());
			data.limitation = e.getValue();
		}
	}
	
	/**
	 * Change the limit value (in bytes) for those
	 * Keys given in the EnumMap.
	 * @param limits Limit values, the default value is zero but since
	 * 				all limits are inactive by default is this value never
	 * 				read.
	 */
	void changeLimits(EnumMap<Keys, Long> limits){
		for(Map.Entry<Keys, Long> e : limits.entrySet()){
			Data data = traffic.get(e.getKey());
			data.limit = e.getValue();
		}		
	}
	
	/**
	 * Reset the traffic to zero when the dates stored
	 * in this traffic object don't correspond to those dates
	 * that are used as input data.
	 * <p>
	 * Yesterday are little special since no date for yesterday
	 * is stored instead the method check if todays date minus one
	 * correspond to the stored date.
	 * 
	 * @param dates Current dates.
	 */
	void changeDate(int[] dates){
		if(dates[Keys.TODAY.number()]-1 == this.dates[Keys.TODAY.number()]) //TODO New Year bug since 356!=1
			traffic.get(Keys.YESTERDAY).copyHere(traffic.get(Keys.TODAY));
		for(int i=0;i<num.length;i++)
			if(dates[i]!=this.dates[i]){
				System.out.println("Reseted: "+Keys.values()[i]);
				this.traffic.get(keys[num[i]]).traffic = 0;
			}
		this.dates = dates.clone();
	}
	
	/**
	 * Update traffic for all keys specified in the update
	 * array in the Enum class Key. The update is done by
	 * adding the method input to the current value.
	 * @param increase Amount of bytes that have increased in last update.
	 */
	void updateTraffic(long increase){
		for(int i=0;i<update.length;i++)
			traffic.get(keys[update[i]]).traffic+=increase;
	}
	
	/**
	 * Data package for traffic data, a data package are
	 * created for every Key. Also keeps track if there is a limit
	 * and what this limit is.
	 */
	private class Data implements TrafficDataPackage, Serializable{
		private static final long serialVersionUID = 1L;
		private long traffic = 0;
		private transient long limit = 0;
		private transient boolean limitation = false;
		
		@Override
		public long getLimit(){
			if(limitation)
				return limit;
			return -1;
		}
		
		@Override
		public long getTraffic(){
			return traffic;
		}
		
		@Override
		public boolean isLimit(){
			return limitation;
		}
		
		/**
		 * Creates a new Data instance that
		 * contains the exact same values as this
		 * instance.
		 * 
		 * @return Copy of this Data instance.
		 */
		private Data copy(){
			Data data = new Data();
			data.traffic = traffic;
			data.limit = limit;
			data.limitation = limitation;
			return data;
		}
		
		/**
		 * Copies the traffic data from the input to
		 * this instance of Data.
		 * @param data From which the traffic data should be copied.
		 */
		private void copyHere(Data data){
			traffic = data.traffic;
		}
	}
}
